# -*- coding: utf-8 -*-
"""
一键把对包含大量顶点和面的模型进行塌陷处理并导出fbx， 主要包括两个操作：Planar Decimate -> Merge By Instance。
用法：
    blender your_file.blend  --background --python decimate_model.py  -- -output_dir /tmp/out/ -input_model xxx.obj -angle_limit 1 -merge_distance 0.0001
注意：
    如果导入的模型是fbx，请注意输出目录不能和输入的目录是同一个目录，避免导出的fbx覆盖原始的fbx
    angle_limit 采用角度
    merge_distance 不能太大
    split_times 可以根据实际模型的大小进行适当调整
"""

import bpy, bmesh
import sys, os
from math import radians
from datetime import datetime,timedelta
import numpy as np

# 获取当前文件的绝对路径，并返回上级目录 
parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__),  '..'))
sys.path.insert(0,  parent_dir)  # 将上级目录添加到搜索路径首部 
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))

from common_func import parse_cli_args, delete_default_objs, export_fbx


def incremental_decimate(obj, angle_steps=[1.0]):
    modified = False
    i = 0
    while i < len(angle_steps):
        angle = angle_steps[i]
        prev_count = len(obj.data.polygons) 
        mod = obj.modifiers.new(f"Decimate_{angle}",  'DECIMATE')
        mod.decimate_type  = 'DISSOLVE'
        mod.angle_limit  = radians(angle)
        # 自动移除原修改器，将效果烘焙到网格数据
        bpy.ops.object.modifier_apply(modifier=mod.name) 
        new_count = len(obj.data.polygons) 
        print(f"Applied angle: {angle}°, {new_count}/{prev_count}={(new_count/prev_count):.2%}")
        if prev_count == new_count:
            if modified:
                break
            else:
                if i == len(angle_steps) - 1:
                    break

                i += 2
                if i >= len(angle_steps):
                    i = len(angle_steps) - 1
        else:
            modified = True  
            i += 1

def incremental_planar_decimate_and_merge(obj:bpy.types.Object, 
                              angle_deg:float=1.0,
                              merge_dist:float=0.0001):
    start_t = datetime.now()
    orig_vertex_count = len(obj.data.vertices)
    me_out_bmesh:bmesh.types.BMesh  

    # 获取网格曲率特征
    curvatures = [abs(v.normal.z)  for v in obj.data.vertices]   # 简化曲率代理 
    curvature_std = np.std(curvatures) 
    print("curvature_std:", curvature_std)

    # 动态调整角度步长
    if curvature_std < 8e-9:
        angle_steps = [1]  
    elif curvature_std < 9e-9:  # 平坦模型 
        angle_steps = [0.5]
    elif curvature_std < 1e-8:
        angle_steps = [0.2]  
    else:  # 高曲率模型 
        angle_steps = [0.02]

    while angle_steps[-1] < angle_deg:
        new_angle = angle_steps[-1] * 2
        if new_angle > angle_deg:
            new_angle = angle_deg
        angle_steps.append(new_angle)

    print(f"modifiers for {obj.name} use angle steps {angle_steps}")
    incremental_decimate(obj, angle_steps)
    me_out_bmesh             \
        = bmesh.new() 
    me_out_bmesh.from_object( 
        object=obj,
        depsgraph=bpy.context.evaluated_depsgraph_get(),
        cage=False,
        face_normals=True,
        )
    
    ###---(A-4) flush bmesh so that we can edit it---
    me_out_bmesh.faces.ensure_lookup_table() 
        
    
    ### Step-B：Merge by Distance ---------------------------------------------
    
    bmesh.ops.remove_doubles( 
        me_out_bmesh,
        verts=me_out_bmesh.verts, 
        dist=merge_dist,
        )
        
    
    ### Step-C：写回原 Mesh ------------------------------------------------------
    
    ###---(C-1) clear原数据---
    orig_me              :bpy.types.Mesh  \
        =(obj if isinstance(obj, bpy.types.Mesh)  else obj.data) 
    
    orig_me.clear_geometry() 
    
    ###---(C-2) bmesh -> Mesh---
    me_out_bmesh.to_mesh(orig_me) 
    
    ###---(C-3) cleanup-----------
    me_out_bmesh.free() 

    orig_me.update() 

    print(f"decimate for {obj.name} success: {len(obj.data.vertices)}/{orig_vertex_count}={(len(obj.data.vertices)/orig_vertex_count):.2%}, took: {(datetime.now()-start_t).seconds} s")


def planar_decimate_and_merge(obj:bpy.types.Object, 
                              angle_deg:float=1.0,
                              merge_dist:float=0.0001):
    start_t = datetime.now()
    orig_vertex_count = len(obj.data.vertices)
    me_out_bmesh:bmesh.types.BMesh  
    
    ### Step-A：Planar Decimate -------------------------------------------------
    
    ###---(A-1) create modifier & apply (C++层直接算)---
    mod_planar               = \
    obj.modifiers.new(name="tmp_planar",  type='DECIMATE')
    mod_planar.decimate_type  ='DISSOLVE'
    mod_planar.angle_limit    = radians(angle_deg)
    mod_planar.use_dissolve_boundaries=False  
    # 自动移除原修改器，将效果烘焙到网格数据
    bpy.ops.object.modifier_apply(modifier=mod_planar.name) 
    
    ###---(A-2) apply to a temporary bmesh---
    me_out_bmesh             \
        = bmesh.new() 
    me_out_bmesh.from_object( 
        object=obj,
        depsgraph=bpy.context.evaluated_depsgraph_get(),
        cage=False,
        face_normals=True,
        )
    # me_out_bmesh.from_mesh(obj.data)

    ###---(A-3) remove modifier---
    # obj.modifiers.remove(mod_planar) 
    
    ###---(A-4) flush bmesh so that we can edit it---
    me_out_bmesh.faces.ensure_lookup_table() 
        
    
    ### Step-B：Merge by Distance ---------------------------------------------
    
    bmesh.ops.remove_doubles( 
        me_out_bmesh,
        verts=me_out_bmesh.verts, 
        dist=merge_dist,
        )
        
    
    ### Step-C：写回原 Mesh ------------------------------------------------------
    
    ###---(C-1) clear原数据---
    orig_me              :bpy.types.Mesh  \
        =(obj if isinstance(obj, bpy.types.Mesh)  else obj.data) 
    
    orig_me.clear_geometry() 
    
    ###---(C-2) bmesh -> Mesh---
    me_out_bmesh.to_mesh(orig_me) 
    
    ###---(C-3) cleanup-----------
    me_out_bmesh.free() 

    orig_me.update() 

    print(f"decimate for {obj.name} success: {len(obj.data.vertices)}/{orig_vertex_count}={(len(obj.data.vertices)/orig_vertex_count):.2%}, took: {(datetime.now()-start_t).seconds} s")



if __name__ == "__main__":
    args = parse_cli_args()
    out_dir = args.get("output_dir", os.getcwd())
    input_model = args.get("input_model") 
    angle_limit = args.get("angle_limit", 1.8)
    merge_distance = args.get("merge_distance", 0.0001)


    if not input_model:
        print(f"您需要输入一个3D模型文件用于切割，输入参数： {args}")
        sys.exit(0)


    delete_default_objs()


    bpy.ops.import_scene.fbx(filepath=str(input_model))
    print(bpy.context.view_layer.objects.active)
    print(bpy.context.selected_objects)
    for obj in bpy.context.scene.objects:
        if obj.type != "MESH":
            continue

        orig_vertex_count = len(obj.data.vertices)
        planar_decimate_and_merge(
            obj,
            angle_deg=float(angle_limit),
            merge_dist=float(merge_distance),
            )
        
        print(f"Done! New vertex count: {len(obj.data.vertices)}/{orig_vertex_count}={(len(obj.data.vertices)/orig_vertex_count):.2%}") 

    out_fbx_path=os.path.join(out_dir, os.path.basename(input_model)[0:-3]+'fbx')
    export_fbx(out_fbx_path)